package com.hctrl.mybatis.interceptor;


import cn.hutool.core.util.ClassUtil;
import com.hctrl.mybatis.interceptor.handler.TypeHandler;
import com.sun.org.apache.xml.internal.utils.ListingErrorHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import java.sql.Statement;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author hanchao
 * @version 1.0
 * @date 2022/5/27 17:45
 */
@Intercepts({@Signature(type = StatementHandler.class,
        method = "parameterize",
        args = {Statement.class})})
@Slf4j
public class SqlPrintInterceptor implements Interceptor {


    private static final String PREFIX = "__frch_";
    private static final String ITEM_SUFFIX = "Item";

    private List<TypeHandler> typeHandlerList = new ArrayList<>();
    {
        //获取TypeHandler接口的所有子类
        String aPackage = ClassUtil.getPackage(TypeHandler.class);
        Set<Class<?>> classes = ClassUtil.scanPackage(aPackage);
        Set<Class<?>> collect = classes.stream().filter(sonClass -> {
            boolean allAssignableFrom = ClassUtil.isAllAssignableFrom(new Class[]{TypeHandler.class}, new Class[]{sonClass});
            //要将 本身排除
            return allAssignableFrom && sonClass != TypeHandler.class;
        }).collect(Collectors.toSet());
        try {
            for (Class<?> clazz : collect){
                Object o = clazz.newInstance();
                typeHandlerList.add((TypeHandler) o);
            }
        }catch (Exception e){
            System.out.println("类扫描异常");
        }
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            StatementHandler target = (StatementHandler) invocation.getTarget();
            BoundSql boundSql = target.getBoundSql();
            String sqlWithMark = boundSql.getSql();
            List<FieldInfo> paramList = parseParamList(boundSql);
            String sql = parseSql(sqlWithMark, paramList);
            printSql(sql);
            return invocation.proceed();
        } catch (Exception e) {
            log.info("HanSqlError : {}", "解析失败");
            return invocation.proceed();
        }
    }

    private void printSql(String sql) {
        log.info("HanSql : {}", sql);
    }

    private String parseSql(String sqlWithMark, List<FieldInfo> paramList) {

        Pattern p = Pattern.compile("\t|\r|\n *");
        Matcher m = p.matcher(sqlWithMark);
        sqlWithMark = m.replaceAll(" ");
        String[] split = sqlWithMark.split("\\?");
        StringBuilder sb = new StringBuilder();
        int length = split.length;
        int size = paramList.size();
        if (size >= 0) {
            int index = 0;
            for (int i = 0; i < length; i++) {
                sb.append(split[i]).append(" ");
                if (index < size) {
                    sb.append(parseParamValue(paramList.get(index))).append(" ");
                    index++;
                }
            }
        }
        return sb.toString();
    }


    private String parseParamValue(FieldInfo fieldInfo){

        for (TypeHandler typeHandler : typeHandlerList){
            if (typeHandler.isSupport(fieldInfo.getFieldType())){
                return typeHandler.handler(fieldInfo.getFieldValue());
            }
        }
        return fieldInfo.getFieldValue().toString();
    }

    private List<FieldInfo> parseParamList(BoundSql boundSql) {
        List<FieldInfo> paramList = new ArrayList<>();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        MetaObject metaObject = MetaObject.forObject(boundSql.getParameterObject(),new DefaultObjectFactory(),new DefaultObjectWrapperFactory(),new DefaultReflectorFactory());
        Map<String,Integer> nameMinMap = new HashMap<>();
        for (ParameterMapping mapping : parameterMappings){
            String propertyName = mapping.getProperty();
            Object value = null;
            Class<?> javaType = mapping.getJavaType();;
            if (propertyName.startsWith(PREFIX)){
                String[] str = propertyName.substring(PREFIX.length()).split("_");
                String name = str[0].replace(ITEM_SUFFIX,"");
                String index = str[1];
                if (nameMinMap.get(name) == null){
                    nameMinMap.put(name,Integer.parseInt(index));
                    value = metaObject.getValue(name + "[" + 0 + "]");
                }else{
                    value = metaObject.getValue(name + "[" + (Integer.parseInt(index) - nameMinMap.get(name)) + "]");
                }
            }else{
                value = metaObject.getValue(propertyName);
                if (javaType.equals(Object.class)){
                    javaType = metaObject.getGetterType(propertyName);
                }
            }
            paramList.add(new FieldInfo(value,javaType));
        }
        return paramList;
    }

    static class FieldInfo{

        private Object fieldValue;

        private Class<?> fieldType;

        public FieldInfo(Object fieldValue, Class<?> fieldType) {
            this.fieldValue = fieldValue;
            this.fieldType = fieldType;
        }

        public Object getFieldValue() {
            return fieldValue;
        }

        public void setFieldNmae(String fieldNmae) {
            fieldValue = fieldNmae;
        }

        public Class<?> getFieldType() {
            return fieldType;
        }

        public void setFieldType(Class<?> fieldType) {
            this.fieldType = fieldType;
        }
    }

    /**
     * 创建代理对象   否则执行器会报空指针异常
     *
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }

}
